package com.github.gaojh.starter.feign;

import cn.hutool.core.util.*;
import com.github.gaojh.config.Environment;
import com.github.gaojh.ioc.IocUtil;
import com.github.gaojh.ioc.annotation.Configuration;
import com.github.gaojh.starter.ServerFace;
import com.github.gaojh.starter.feign.annotation.FeignClient;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.discovery.EurekaClient;
import com.netflix.loadbalancer.*;
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.niws.loadbalancer.EurekaNotificationServerListUpdater;
import feign.Client;
import feign.Feign;
import feign.Logger;
import feign.Request;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.ribbon.LBClient;
import feign.ribbon.LBClientFactory;
import feign.ribbon.RibbonClient;
import feign.slf4j.Slf4jLogger;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author gaojh
 * @since 2020/7/29
 */
@Configuration
@Slf4j
public class FeignStarter implements ServerFace {

    protected static String PRE = "feign.";

    public static final String PROP_URL = PRE + "url";

    /**
     * 日志级别 "none", "basic", "headers", "full"
     */
    public static final String PROP_LOGLEVEL = PRE + "logLevel";
    /**
     * 客户端实现类 okhttp, ribbon
     */
    public static final String PROP_CLIENT = PRE + "client";
    /**
     * 负载均衡
     */
    public static final String PROP_LB_RULE = PRE + "loadbalancer";
    /**
     * 连接超时 10000
     */
    public static final String PROP_CONNECT_TIMEOUT = PRE + "connectTimeout";

    /**
     * 读取超时 10000
     */
    public static final String PROP_READ_TIMEOUT = PRE + "readTimeout";

    @Override
    public boolean start() throws Exception {
        List<Object> beans = IocUtil.getAllBeans();
        for (Object o : beans) {
            for (Field field : o.getClass().getDeclaredFields()) {
                FeignClient fc = field.getAnnotation(FeignClient.class);
                if (fc == null) {
                    continue;
                }

                String url = fc.baseUrl();
                if (StrUtil.isBlank(url)) {
                    url = Environment.me().getString(PROP_URL, "http://127.0.0.1:8080");
                }

                Encoder encoder = new JacksonEncoder();
                Decoder decoder = new JacksonDecoder();

                Client client = getClient(fc, url);

                Feign.Builder builder = Feign.builder();
                Logger.Level level = Logger.Level.valueOf(Environment.me().getString("", "BASIC").toUpperCase());

                builder.encoder(encoder).decoder(decoder).client(client).logger(new Slf4jLogger(field.getType())).logLevel(level);

                int connectTimeout = fc.connectTimeout();
                if (connectTimeout == 0) {
                    connectTimeout = Environment.me().getInteger(PROP_CONNECT_TIMEOUT, 60);
                }
                int readTimeout = fc.readTimeout();
                if (readTimeout == 0) {
                    readTimeout = Environment.me().getInteger(PROP_READ_TIMEOUT, 60);
                }

                builder.options(new Request.Options(connectTimeout, TimeUnit.SECONDS, readTimeout, TimeUnit.SECONDS, true));
                Object target = builder.target(field.getType(), url);
                ReflectUtil.setFieldValue(o, field, target);
            }
        }
        return true;
    }

    protected Client getClient(FeignClient fc, String url) {
        String clientStr = fc.client();
        if (StrUtil.isBlank(clientStr)) {
            clientStr = Environment.me().getString(PROP_CLIENT, "okhttp");
        }
        if ("ribbon".equals(clientStr)) {
            LBClient lb = (LBClient) getLoadBalancer(URI.create(url).getHost(), fc);
            return RibbonClient.builder().lbClientFactory(clientName -> lb).build();
        }
        return new OkHttpClient();
    }



    public Object getLoadBalancer(String name, FeignClient fc) {
        EurekaClient eurekaClient = IocUtil.getBean(EurekaClient.class, "eurekaClient");
        DefaultClientConfigImpl clientConfig = DefaultClientConfigImpl.getClientConfigWithDefaultValues(name);
        ServerList<DiscoveryEnabledServer> list = new DiscoveryEnabledNIWSServerList(name, () -> eurekaClient);
        ServerListFilter<DiscoveryEnabledServer> filter = new ZoneAffinityServerListFilter<>(clientConfig);
        ServerListUpdater updater = new EurekaNotificationServerListUpdater(() -> eurekaClient);

        IRule rule;
        String loadBalancer = fc.loadbalancer();
        if (StrUtil.isBlank(loadBalancer)) {
            loadBalancer = Environment.me().getString(PROP_LB_RULE, "random");
        }
        if ("random".equals(loadBalancer)) {
            rule = new RandomRule();
        } else {
            AvailabilityFilteringRule avaRule = new AvailabilityFilteringRule();
            avaRule.initWithNiwsConfig(clientConfig);
            rule = avaRule;
        }
        ZoneAwareLoadBalancer<DiscoveryEnabledServer> lb = LoadBalancerBuilder.<DiscoveryEnabledServer>newBuilder()
                .withDynamicServerList(list)
                .withRule(rule)
                .withServerListFilter(filter)
                .withServerListUpdater(updater)
                .withClientConfig(clientConfig)
                .buildDynamicServerListLoadBalancerWithUpdater();
        return LBClient.create(lb, clientConfig);
    }

    @Override
    public boolean stop() throws Exception {
        return true;
    }
}
